/*
 * Copyright 2019-2025 the original author or authors.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iiifi.kite.boot.configuration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.iiifi.kite.boot.properties.KiteSwaggerProperties;
import com.iiifi.kite.configuration.KiteProperties;

import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;

/**
 * Swagger3配置
 *
 * @author kite@iiifi.com 花朝
 */
@Configuration
@RequiredArgsConstructor
@ConditionalOnClass(Docket.class)
@AutoConfigureAfter(KiteSwaggerProperties.class)
@ConditionalOnProperty(value = "kite.swagger.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingClass("org.springframework.cloud.gateway.configuration.GatewayAutoConfiguration")
public class KiteSwaggerAutoConfiguration {
    private final KiteProperties kiteProperties;
    private final KiteSwaggerProperties swaggerProperties;

    @Bean
    public Docket createRestApi() {
        // 组名为应用名
        String appName = kiteProperties.getName();
        Docket docket = new Docket(DocumentationType.OAS_30).useDefaultResponseMessages(false)
                .globalRequestParameters(globalHeaders()).apiInfo(apiInfo(appName)).select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).paths(PathSelectors.any()).build();
        // 如果开启认证
        if (swaggerProperties.getAuthorization().getEnabled()) {
            docket.securitySchemes(Collections.singletonList(apiKey()));
            docket.securityContexts(Collections.singletonList(securityContext()));
        }
        return docket;
    }

    /**
     * 配置基于 ApiKey 的鉴权对象
     *
     * @return {ApiKey}
     */
    private ApiKey apiKey() {
        return new ApiKey(swaggerProperties.getAuthorization().getName(),
                swaggerProperties.getAuthorization().getKeyName(), ApiKeyVehicle.HEADER.getValue());
    }

    /**
     * 配置默认的全局鉴权策略的开关，以及通过正则表达式进行匹配；默认 ^.*$ 匹配所有URL 其中 securityReferences 为配置启用的鉴权策略
     *
     * @return {SecurityContext}
     */
    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(swaggerProperties.getAuthorization().getAuthRegex())).build();
    }

    /**
     * 配置默认的全局鉴权策略；其中返回的 SecurityReference 中，reference
     * 即为ApiKey对象里面的name，保持一致才能开启全局鉴权
     *
     * @return {List<SecurityReference>}
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Collections.singletonList(SecurityReference.builder()
                .reference(swaggerProperties.getAuthorization().getName()).scopes(authorizationScopes).build());
    }

    private ApiInfo apiInfo(String appName) {
        String defaultName = appName + " 服务";
        String title = Optional.ofNullable(swaggerProperties.getTitle()).orElse(defaultName);
        String description = Optional.ofNullable(swaggerProperties.getDescription()).orElse(defaultName);
        return new ApiInfoBuilder().title(title).description(description).version(swaggerProperties.getVersion())
                .contact(new Contact(swaggerProperties.getContactUser(), swaggerProperties.getContactUrl(),
                        swaggerProperties.getContactEmail()))
                .build();
    }

    private List<RequestParameter> globalHeaders() {
        List<RequestParameter> pars = new ArrayList<>();
        getHeaders().forEach(header -> {
            RequestParameter parameter = new RequestParameterBuilder().name(header.getName())
                    .description(header.getDescription()).in(ParameterType.HEADER)
                    .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING))).required(header.isRequired()).build();
            pars.add(parameter);
        });
        return pars;
    }

    /**
     * 设置默认header
     *
     * @return headers
     */
    private List<KiteSwaggerProperties.Header> getHeaders() {
        List<KiteSwaggerProperties.Header> headers = swaggerProperties.getHeaders();
        KiteSwaggerProperties.Header tokenHeader = new KiteSwaggerProperties.Header();
        tokenHeader.setName(kiteProperties.getHeaders().getToken());
        headers.add(tokenHeader);
        KiteSwaggerProperties.Header deviceHeader = new KiteSwaggerProperties.Header();
        deviceHeader.setName(kiteProperties.getHeaders().getDeviceId());
        headers.add(deviceHeader);
        return headers;
    }
}
